﻿using System;
using System.Threading.Tasks;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Practices.Unity;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
#if WINDOWS_PHONE_APP
using Windows.UI.Xaml.Controls;
using Windows.Phone.UI.Input;
#endif

namespace Microsoft.Practices.Prism.Mvvm
{
	/// <summary>
	/// Provides the base class for the Windows Store Application object which
	/// includes the automatic creation and wiring of the Unity container.
	/// </summary>
	public abstract class MvvmUnityAppBase : MvvmAppBase
	{
		private IUnityContainer _container = null;

		/// <summary>
		/// Creates a new instance of MvvmUnityAppBase.
		/// </summary>
		public MvvmUnityAppBase()
		{
			// ***
			// *** Create the container
			// ***
			_container = new UnityContainer();

			// ***
			// *** Register the unity container
			// ***
			_container.RegisterInstance<IUnityContainer>(_container);

			// ***
			// *** Set up the global locator service
			// ***
			ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(_container));

			// ***
			// *** Allow the implementation class the opportunity to register
			// *** types early in the process. Do not allow exceptions to abort
			// *** the object creation.
			// ***
			try
			{
				this.OnEarlyContainerRegistration(_container);
			}
			catch (Exception ex)
			{
				this.OnUnhandledRegistrationException(ex);
			}
		}

		/// <summary>
		/// Get an reference to the current Application instance
		/// as an MvvmUnityAppBase object.
		/// </summary>
		public static new MvvmUnityAppBase Current
		{
			get
			{
				return (MvvmUnityAppBase)Application.Current;
			}
		}

		/// <summary>
		/// Get the IoC Unity Container 
		/// </summary>
		public IUnityContainer Container
		{
			get
			{
				return _container;
			}
		}

		/// <summary>
		/// Implements and seals the OnInitialize method. The implementation
		/// of this method calls a new OnApplcationInitialize method.
		/// </summary>
		/// <param name="args">The <see cref="IActivatedEventArgs"/> instance containing the event data.</param>
		protected sealed override Task OnInitializeAsync(IActivatedEventArgs args)
		{
			try
			{
				// ***
				// *** Allow the implementing class the opportunity to
				// *** register types. Do not allow exceptions to abort
				// *** the initialization process.
				// ***
				try
				{
					this.OnContainerRegistration((UnityContainer)_container);
				}
				catch (Exception ex)
				{
					this.OnUnhandledRegistrationException(ex);
				}

				// ***
				// *** Set the ViewModel Locator service to use the Unity Container
				// ***
				ViewModelLocationProvider.SetDefaultViewModelFactory((viewModelType) =>
				{
					return ServiceLocator.Current.GetInstance(viewModelType);
				});
			}
			finally
			{
				this.OnApplicationInitialize(args);
			}

			return base.OnInitializeAsync(args);
		}

		/// <summary>
		/// Override this method with the initialization logic of your application. Here you can initialize 
		/// services, repositories, and so on.
		/// </summary>
		/// <param name="args">The <see cref="IActivatedEventArgs"/> instance containing the event data.</param>
		protected virtual void OnApplicationInitialize(IActivatedEventArgs args)
		{

		}

		/// <summary>
		/// Implements and seals the Resolves method to be handled by the Unity Container.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns>A concrete instance of the specified type.</returns>
		protected sealed override object Resolve(Type type)
		{
			// ***
			// *** Use the container to resolve types (e.g. ViewModels and Flyouts)
			// *** so their dependencies get injected
			// ***
			return ServiceLocator.Current.GetInstance<IUnityContainer>().Resolve(type);
		}

		/// <summary>
		/// Override this method with code to initialize your container. This method will contain calls
		/// to the Unity container's RegisterType and RegisterInstance methods for example.
		/// </summary>
		/// <param name="container">The instance of the unity container that should be used for registering types.</param>
		protected virtual void OnContainerRegistration(IUnityContainer container)
		{
		}

		/// <summary>
		/// Override this method to register types in the container prior to any other code
		/// being run. This is especially useful when types need to be registered for application
		/// session state to be restored. Certain types may not be available or should not be registered
		/// in this method. For example, registering the Pub/Sub 
		/// </summary>
		/// <param name="container">The instance of the unity container that should be used for registering types.</param>
		protected virtual void OnEarlyContainerRegistration(IUnityContainer container)
		{
		}

		/// <summary>
		/// Override this method to catch any unhandled exceptions that occur during the registration process.
		/// </summary>
		/// <param name="ex">The exception that was thrown.</param>
		protected virtual void OnUnhandledRegistrationException(Exception ex)
		{
		}

#if WINDOWS_PHONE_APP
		/// <summary>
		/// Overrides the OnHardwareButtonsBackPressed method and checks the current
		/// page for a view model that implements IBackButtonIntercept. If found it calls
		/// this interface to determine what to do. Otherwise it executes the default
		/// logic.
		/// </summary>
		protected override void OnHardwareButtonsBackPressed(object sender, BackPressedEventArgs e)
		{
			IBackButtonIntercept currentObject = this.GetBackButtonInterceptObject();

			if (currentObject != null)
			{
				currentObject.BackPressed(sender, e);

				if (!e.Handled)
				{
					base.OnHardwareButtonsBackPressed(sender, e);
				}
			}
			else
			{
				base.OnHardwareButtonsBackPressed(sender, e);
			}
		}

		/// <summary>
		/// Finds a view model in the current page that implements IBackButtonIntercept (if
		/// it exists). returns null if not found.
		/// </summary>
		private IBackButtonIntercept GetBackButtonInterceptObject()
		{
			IBackButtonIntercept returnValue = null;

			object currentFrame = Window.Current != null ? Window.Current.Content : null;
			ContentControl content = currentFrame as ContentControl;

			if (content != null)
			{
				IView view = content.Content as IView;

				if (view != null)
				{
					if (view.DataContext is IBackButtonIntercept)
					{
						IBackButtonIntercept backButtonIntercept = view.DataContext as IBackButtonIntercept;

						if (backButtonIntercept != null)
						{
							returnValue = backButtonIntercept;
						}
					}
				}
			}

			return returnValue;
		}
#endif
	}
}
